/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.reorg; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.resources.IResource; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IBuffer; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.ToolFactory; import org.eclipse.jdt.core.compiler.IScanner; import org.eclipse.jdt.core.compiler.ITerminalSymbols; import org.eclipse.jdt.core.compiler.InvalidInputException; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.TypeReferenceMatch; import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.jdt.internal.corext.refactoring.CollectingSearchRequestor; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory; import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine; import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup; import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext; import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility; import org.eclipse.jdt.internal.corext.refactoring.structure.ReferenceFinderUtil; import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.corext.util.SearchUtils; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; public class MoveCuUpdateCreator { private final String fNewPackage; private ICompilationUnit[] fCus; private IPackageFragment fDestination; private Map<ICompilationUnit, ImportRewrite> fImportRewrites; //ICompilationUnit -> ImportEdit public MoveCuUpdateCreator(ICompilationUnit cu, IPackageFragment pack){ this(new ICompilationUnit[]{cu}, pack); } public MoveCuUpdateCreator(ICompilationUnit[] cus, IPackageFragment pack){ Assert.isNotNull(cus); Assert.isNotNull(pack); fCus= cus; fDestination= pack; fImportRewrites= new HashMap<ICompilationUnit, ImportRewrite>(); fNewPackage= fDestination.isDefaultPackage() ? "" : fDestination.getElementName() + '.'; //$NON-NLS-1$ } public TextChangeManager createChangeManager(IProgressMonitor pm, RefactoringStatus status) throws JavaModelException{ pm.beginTask("", 5); //$NON-NLS-1$ try{ TextChangeManager changeManager= new TextChangeManager(); addUpdates(changeManager, new SubProgressMonitor(pm, 4), status); addImportRewriteUpdates(changeManager); return changeManager; } catch (JavaModelException e){ throw e; } catch (CoreException e){ throw new JavaModelException(e); } finally{ pm.done(); } } private void addImportRewriteUpdates(TextChangeManager changeManager) throws CoreException { for (Iterator<ICompilationUnit> iter= fImportRewrites.keySet().iterator(); iter.hasNext();) { ICompilationUnit cu= iter.next(); ImportRewrite importRewrite= fImportRewrites.get(cu); if (importRewrite != null && importRewrite.hasRecordedChanges()) { TextChangeCompatibility.addTextEdit(changeManager.get(cu), RefactoringCoreMessages.MoveCuUpdateCreator_update_imports, importRewrite.rewriteImports(null)); } } } private void addUpdates(TextChangeManager changeManager, IProgressMonitor pm, RefactoringStatus status) throws CoreException { pm.beginTask("", fCus.length); //$NON-NLS-1$ for (int i= 0; i < fCus.length; i++){ if (pm.isCanceled()) throw new OperationCanceledException(); addUpdates(changeManager, fCus[i], new SubProgressMonitor(pm, 1), status); } } private void addUpdates(TextChangeManager changeManager, ICompilationUnit movedUnit, IProgressMonitor pm, RefactoringStatus status) throws CoreException{ try{ pm.beginTask("", 3); //$NON-NLS-1$ pm.subTask(Messages.format(RefactoringCoreMessages.MoveCuUpdateCreator_searching, BasicElementLabels.getFileName(movedUnit))); if (isInAnotherFragmentOfSamePackage(movedUnit, fDestination)){ pm.worked(3); return; } addImportToSourcePackageTypes(movedUnit, new SubProgressMonitor(pm, 1)); removeImportsToDestinationPackageTypes(movedUnit); addReferenceUpdates(changeManager, movedUnit, new SubProgressMonitor(pm, 2), status); } finally{ pm.done(); } } private void addReferenceUpdates(TextChangeManager changeManager, ICompilationUnit movedUnit, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException, CoreException { List<ICompilationUnit> cuList= Arrays.asList(fCus); SearchResultGroup[] references= getReferences(movedUnit, pm, status); for (int i= 0; i < references.length; i++) { SearchResultGroup searchResultGroup= references[i]; ICompilationUnit referencingCu= searchResultGroup.getCompilationUnit(); if (referencingCu == null) continue; boolean simpleReferencesNeedNewImport= simpleReferencesNeedNewImport(movedUnit, referencingCu, cuList); SearchMatch[] results= searchResultGroup.getSearchResults(); for (int j= 0; j < results.length; j++) { // TODO: should update type references with results from addImport TypeReference reference= (TypeReference) results[j]; if (reference.isImportDeclaration()) { ImportRewrite rewrite= getImportRewrite(referencingCu); IImportDeclaration importDecl= (IImportDeclaration) SearchUtils.getEnclosingJavaElement(results[j]); if (Flags.isStatic(importDecl.getFlags())) { rewrite.removeStaticImport(importDecl.getElementName()); addStaticImport(movedUnit, importDecl, rewrite); } else { rewrite.removeImport(importDecl.getElementName()); rewrite.addImport(createStringForNewImport(movedUnit, importDecl)); } } else if (reference.isQualified()) { TextChange textChange= changeManager.get(referencingCu); String changeName= RefactoringCoreMessages.MoveCuUpdateCreator_update_references; TextEdit replaceEdit= new ReplaceEdit(reference.getOffset(), reference.getSimpleNameStart() - reference.getOffset(), fNewPackage); TextChangeCompatibility.addTextEdit(textChange, changeName, replaceEdit); } else if (simpleReferencesNeedNewImport) { ImportRewrite importEdit= getImportRewrite(referencingCu); String typeName= reference.getSimpleName(); importEdit.addImport(getQualifiedType(fDestination.getElementName(), typeName)); } } } } private void addStaticImport(ICompilationUnit movedUnit, IImportDeclaration importDecl, ImportRewrite rewrite) { String old= importDecl.getElementName(); int oldPackLength= movedUnit.getParent().getElementName().length(); StringBuffer result= new StringBuffer(fDestination.getElementName()); if (oldPackLength == 0) // move FROM default package result.append('.').append(old); else if (result.length() == 0) // move TO default package result.append(old.substring(oldPackLength + 1)); // cut "." else result.append(old.substring(oldPackLength)); int index= result.lastIndexOf("."); //$NON-NLS-1$ if (index > 0 && index < result.length() - 1) rewrite.addStaticImport(result.substring(0, index), result.substring(index + 1, result.length()), true); } private String getQualifiedType(String packageName, String typeName) { if (packageName.length() == 0) return typeName; else return packageName + '.' + typeName; } private String createStringForNewImport(ICompilationUnit movedUnit, IImportDeclaration importDecl) { String old= importDecl.getElementName(); int oldPackLength= movedUnit.getParent().getElementName().length(); StringBuffer result= new StringBuffer(fDestination.getElementName()); if (oldPackLength == 0) // move FROM default package result.append('.').append(old); else if (result.length() == 0) // move TO default package result.append(old.substring(oldPackLength + 1)); // cut "." else result.append(old.substring(oldPackLength)); return result.toString(); } private void removeImportsToDestinationPackageTypes(ICompilationUnit movedUnit) throws CoreException{ ImportRewrite importEdit= getImportRewrite(movedUnit); IType[] destinationTypes= getDestinationPackageTypes(); for (int i= 0; i < destinationTypes.length; i++) { importEdit.removeImport(destinationTypes[i].getFullyQualifiedName('.')); } } private IType[] getDestinationPackageTypes() throws JavaModelException { List<IType> types= new ArrayList<IType>(); if (fDestination.exists()) { ICompilationUnit[] cus= fDestination.getCompilationUnits(); for (int i= 0; i < cus.length; i++) { types.addAll(Arrays.asList(cus[i].getAllTypes())); } } return types.toArray(new IType[types.size()]); } private void addImportToSourcePackageTypes(ICompilationUnit movedUnit, IProgressMonitor pm) throws CoreException{ List<ICompilationUnit> cuList= Arrays.asList(fCus); IType[] allCuTypes= movedUnit.getAllTypes(); IType[] referencedTypes= ReferenceFinderUtil.getTypesReferencedIn(allCuTypes, pm); ImportRewrite importEdit= getImportRewrite(movedUnit); importEdit.setFilterImplicitImports(false); IPackageFragment srcPack= (IPackageFragment)movedUnit.getParent(); for (int i= 0; i < referencedTypes.length; i++) { IType iType= referencedTypes[i]; if (! iType.exists()) continue; if (!JavaModelUtil.isSamePackage(iType.getPackageFragment(), srcPack)) continue; if (cuList.contains(iType.getCompilationUnit())) continue; importEdit.addImport(iType.getFullyQualifiedName('.')); } } private ImportRewrite getImportRewrite(ICompilationUnit cu) throws CoreException{ if (fImportRewrites.containsKey(cu)) return fImportRewrites.get(cu); ImportRewrite importEdit= StubUtility.createImportRewrite(cu, true); fImportRewrites.put(cu, importEdit); return importEdit; } private boolean simpleReferencesNeedNewImport(ICompilationUnit movedUnit, ICompilationUnit referencingCu, List<ICompilationUnit> cuList) { if (referencingCu.equals(movedUnit)) return false; if (cuList.contains(referencingCu)) return false; if (isReferenceInAnotherFragmentOfSamePackage(referencingCu, movedUnit)) { /* Destination package is different from source, since * isDestinationAnotherFragmentOfSamePackage(movedUnit) was false in addUpdates(.) */ return true; } //heuristic if (referencingCu.getImport(movedUnit.getParent().getElementName() + ".*").exists()) //$NON-NLS-1$ return true; // has old star import if (referencingCu.getParent().equals(movedUnit.getParent())) return true; //is moved away from same package return false; } private boolean isReferenceInAnotherFragmentOfSamePackage(ICompilationUnit referencingCu, ICompilationUnit movedUnit) { if (referencingCu == null) return false; if (! (referencingCu.getParent() instanceof IPackageFragment)) return false; IPackageFragment pack= (IPackageFragment) referencingCu.getParent(); return isInAnotherFragmentOfSamePackage(movedUnit, pack); } private static boolean isInAnotherFragmentOfSamePackage(ICompilationUnit cu, IPackageFragment pack) { if (! (cu.getParent() instanceof IPackageFragment)) return false; IPackageFragment cuPack= (IPackageFragment) cu.getParent(); return ! cuPack.equals(pack) && JavaModelUtil.isSamePackage(cuPack, pack); } private static SearchResultGroup[] getReferences(ICompilationUnit unit, IProgressMonitor pm, RefactoringStatus status) throws CoreException { final SearchPattern pattern= RefactoringSearchEngine.createOrPattern(unit.getTypes(), IJavaSearchConstants.REFERENCES); if (pattern != null) { String binaryRefsDescription= Messages.format(RefactoringCoreMessages.ReferencesInBinaryContext_ref_in_binaries_description , BasicElementLabels.getFileName(unit)); ReferencesInBinaryContext binaryRefs= new ReferencesInBinaryContext(binaryRefsDescription); Collector requestor= new Collector(((IPackageFragment) unit.getParent()), binaryRefs); IJavaSearchScope scope= RefactoringScopeFactory.create(unit, true, false); SearchResultGroup[] result= RefactoringSearchEngine.search(pattern, scope, requestor, new SubProgressMonitor(pm, 1), status); binaryRefs.addErrorIfNecessary(status); return result; } return new SearchResultGroup[] {}; } private final static class Collector extends CollectingSearchRequestor { private IPackageFragment fSource; private IScanner fScanner; public Collector(IPackageFragment source, ReferencesInBinaryContext binaryRefs) { super(binaryRefs); fSource= source; fScanner= ToolFactory.createScanner(false, false, false, false); } /* (non-Javadoc) * @see org.eclipse.jdt.internal.corext.refactoring.CollectingSearchRequestor#acceptSearchMatch(SearchMatch) */ @Override public void acceptSearchMatch(SearchMatch match) throws CoreException { if (filterMatch(match)) return; /* * Processing is done in collector to reuse the buffer which was * already required by the search engine to locate the matches. */ // [start, end[ include qualification. IJavaElement element= SearchUtils.getEnclosingJavaElement(match); int accuracy= match.getAccuracy(); int start= match.getOffset(); int length= match.getLength(); boolean insideDocComment= match.isInsideDocComment(); IResource res= match.getResource(); if (element.getAncestor(IJavaElement.IMPORT_DECLARATION) != null) { collectMatch(TypeReference.createImportReference(element, accuracy, start, length, insideDocComment, res)); } else { ICompilationUnit unit= (ICompilationUnit) element.getAncestor(IJavaElement.COMPILATION_UNIT); if (unit != null) { IBuffer buffer= unit.getBuffer(); String matchText= buffer.getText(start, length); if (fSource.isDefaultPackage()) { collectMatch(TypeReference.createSimpleReference(element, accuracy, start, length, insideDocComment, res, matchText)); } else { // assert: matchText doesn't start nor end with comment int simpleNameStart= getLastSimpleNameStart(matchText); if (simpleNameStart != 0) { collectMatch(TypeReference.createQualifiedReference(element, accuracy, start, length, insideDocComment, res, start + simpleNameStart)); } else { collectMatch(TypeReference.createSimpleReference(element, accuracy, start, length, insideDocComment, res, matchText)); } } } } } private int getLastSimpleNameStart(String reference) { fScanner.setSource(reference.toCharArray()); int lastIdentifierStart= -1; try { int tokenType= fScanner.getNextToken(); while (tokenType != ITerminalSymbols.TokenNameEOF) { if (tokenType == ITerminalSymbols.TokenNameIdentifier) lastIdentifierStart= fScanner.getCurrentTokenStartPosition(); tokenType= fScanner.getNextToken(); } } catch (InvalidInputException e) { JavaPlugin.log(e); } return lastIdentifierStart; } } private final static class TypeReference extends TypeReferenceMatch { private String fSimpleTypeName; private int fSimpleNameStart; private TypeReference(IJavaElement enclosingElement, int accuracy, int start, int length, boolean insideDocComment, IResource resource, int simpleNameStart, String simpleName) { super(enclosingElement, accuracy, start, length, insideDocComment, SearchEngine.getDefaultSearchParticipant(), resource); fSimpleNameStart= simpleNameStart; fSimpleTypeName= simpleName; } public static TypeReference createQualifiedReference(IJavaElement enclosingElement, int accuracy, int start, int length, boolean insideDocComment, IResource resource, int simpleNameStart) { Assert.isTrue(start < simpleNameStart && simpleNameStart < start + length); return new TypeReference(enclosingElement, accuracy, start, length, insideDocComment, resource, simpleNameStart, null); } public static TypeReference createImportReference(IJavaElement enclosingElement, int accuracy, int start, int length, boolean insideDocComment, IResource resource) { return new TypeReference(enclosingElement, accuracy, start, length, insideDocComment, resource, -1, null); } public static TypeReference createSimpleReference(IJavaElement enclosingElement, int accuracy, int start, int length, boolean insideDocComment, IResource resource, String simpleName) { return new TypeReference(enclosingElement, accuracy, start, length, insideDocComment, resource, -1, simpleName); } public boolean isImportDeclaration() { return SearchUtils.getEnclosingJavaElement(this).getAncestor(IJavaElement.IMPORT_DECLARATION) != null; } public boolean isQualified() { return fSimpleNameStart != -1; } /** * @return start offset of simple type name, or -1 iff ! isQualified() */ public int getSimpleNameStart() { return fSimpleNameStart; } /** * @return simple type name, or null iff ! isSimpleName() */ public String getSimpleName() { return fSimpleTypeName; } } }